/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.situations;

import java.util.Arrays;

import robocup.component.DAInamiteConstants;
import robocup.component.Prophet;
import robocup.component.SConf;
import robocup.component.Synchro;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.KickAction;
import robocup.component.geometry.Vektor;
import robocup.component.worldmodel.PlayersModel;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.UPDATE_MODE;

/**
 * This Situation calculates if passing is possible, what teammate to pass
 * to and where to pass to. It uses a simple algorithm:
 * <ol>
 *  <li>choose a limited number of pass directions</li>
 *  <li>simulate kicks with different power for each direction</li>
 *  <li>determine if a teammate gets the simulated ball first and where he 
 *  gets it</li>
 *  <li>calculate the benefit for the resulting passes using the 
 *  {@link SituationEvaluator} for all successful passes</li>
 *  <li>choose the pass with the highest benefit</li>
 * </ol>
 * 
 *  Because of all the simulating the pass situation is one of the most 
 *  time crucial Situations and is therefore very much optimized for efficiency. 
 */
public class PassSituation extends Situation {

    private int emptyInt = Integer.MAX_VALUE;

    //the receivers number
    private int passTo = this.emptyInt;

    //the resulting IP
    private Vektor resIP = new Vektor();

    //the ball resulting from the pass
    private Ball resBall = new Ball(-1, -1, new Vektor(), new Vektor());

    //the best pass direction
    private int bestDir;

    // security in m for pass calculation (teammates)
    double securityMates = 0.6;

    // security in m for pass calculation (opponents)
    double securityOthers = 0.0;

    //number of pass directions
    private int numberPassDirs = 60;

    // max. considered cycles
    private int maxCycles = DAInamiteConstants.PROPHET_MAX_CYCLES_PASSING;
    /**
     * Ref to {@link PlayersModel#getExpectedPlayers()}
     */
    private Player[] expectedPlayers;
    /**
     * the body angle for all players
     */
    private double[] bodyDirs = new double[22];
    /**
     * if false don't pass to this teammate
     */
    private boolean[] isFilteredTeammate = new boolean[11];
    /**
     * try kicking with these percentages of the max kick.
     */
    private double[] accelPercentage = new double[]{1.0, 0.85, 0.6};
    /**
     * the resulting interception points for each direction.
     */
    private Vektor[] interceptPoints = new Vektor[this.numberPassDirs];
    /**
     * the resulting interception times for each direction.
     */
    private int[] interceptTimes = new int[this.numberPassDirs];
    /**
     * the resulting intercepting player for each direction as number.
     */
    private int[] interceptingPlayer = new int[this.numberPassDirs];
    /**
     * the resulting intercepting opponent intercepting after a teammates.
     * Used for telling how secure the pass is.
     */
    private int[] nextInterceptingOpp = new int[this.numberPassDirs];
    /**
     * the ball position in each cycle by direction
     */
    private Vektor[] ballPosition = new Vektor[this.numberPassDirs];
    /**
     * the ballspedd in each cycle by direction.
     */
    private Vektor[] ballSpeed = new Vektor[this.numberPassDirs];
    /**
     * the max. resulting ball speed in each direction
     */
    private Vektor[] maxBallSpeed = new Vektor[this.numberPassDirs];
    /**
     * the directions to simulate.
     */
    private Vektor[] dirs = new Vektor[this.numberPassDirs];
    /**
     * the abs. angles of <code>dirs</code>.
     */
    private double[] dirAngles = new double[this.numberPassDirs];
    /**
     * The distribution of direction.
     *
     * Based on the line from the ball to the opponents goal each area of 45
     * degree is given a weight. Directions in the opponents goal direction are
     * prefered.
     */
    private double[] dirImportance = new double[]{1.0 / 6.0, 1.0 / 6.0,
        2.0 / 15.0, 1.0 / 30.0};
    /**
     * the offset in cycles for each player. used as players that were not seen
     * might have reacted in these cycle.
     */
    private int[] playerOffset = new int[22];
    /**
     * the maximal turn angles by player turning 1x or 2x. Depends on the players
     * velocity.
     */
    private double[][] maxTurnAngle = new double[22][2];
    /**
     * the (extrapolated) player positions
     */
    private Vektor[] playerPosition = Vektor.getDummyInstances(22);
    /**
     * the (extrapolated) players' speed
     */
    private Vektor[] playerSpeed = Vektor.getDummyInstances(22);
    /**
     * consider the direction for further simulation.
     */
    private boolean[] considerDir = new boolean[this.numberPassDirs];
    /**
     * filter this direction. not considering it at all.
     */
    private boolean[] filteredDirs = new boolean[this.numberPassDirs];
    /**
     * the time since last watching the direction.
     */
    private double[] dirAge = new double[this.numberPassDirs];
    /**
     * the distance between player and ball considered as secure (interception).
     */
    private double[] secureDistance = new double[22];
    /**
     * used for choosing initial directions
     */
    private int[] numberDirsLeft = new int[4];
    /**
     * used for choosing initial directions
     */
    private int[] numberDirsRight = new int[4];
    /**
     * consider a player for a given direction
     */
    private boolean[][] considerPlayers = new boolean[this.numberPassDirs][22];
    /**
     * al angles to players
     */
    private double[] angleToPlayer = new double[22];
    /**
     * all distances to players
     */
    private double[] distanceToPlayer = new double[22];
    /**
     * avoid heavy Vektor creation for better performance.
     */
    private Vektor dummyVektor = new Vektor();
    /**
     * the overlap when intercepting. the more overlap the more secure is the pass
     *  (teammates)
     */
    private double[] interceptDistanceOwn = new double[this.numberPassDirs];
    /**
     * the overlap when intercepting. the more overlap the more secure is the pass
     *  (opponents)
     */
    private double[] interceptDistanceOpponent = new double[this.numberPassDirs];
    /**
     * if we are late we calculate less.
     */
    private Synchro synchro;
    /**
     * the resulting interception time.
     */
    private int resInterceptionTime;
    /**
     * the resulting distance between the interception point and the nearest
     * extrapolated opponent.
     */
    private double resDistanceOpponent;
    /**
     * the resulting distance between the interception point and the nearest
     * extrapolated teammate.
     */
    private double resDistanceOwn;
    /**
     * the resulting distance between the interception point and the nearest
     * extrapolated opponent when a teammate intercepts first.
     */
    private double resNearestOpponentDistance;
    /**
     * the resulting distances between the interception point and the nearest
     * extrapolated opponent when a teammate intercepts first.
     */
    private double[] nearestOpponenentDistance = new double[this.numberPassDirs];
    /**
     * players with more than this angle difference from the pass dir
     * are filtered if they are far
     */
    private static double MAX_TOLERATED_ANGLE_TO_PLAYER = 90;
    /**
     * near players are not filtered as they may intercept balls in all dirs!
     */
    private static double MAX_NEAR_DISTANCE_TO_PLAYER = 4;

    /**
     * Constructor - a situation, which simulates different possible passes.
     *
     * @param world -
     *            the current world-model
     * @param prophet -
     *            the predicting device (parent object)
     * @param movements -
     *            a generic movement model
     * @param synchro -
     *            the synchro object, which can be used to estimate the time
     *            left for calculations
     */
    public PassSituation(WorldModel world, Prophet prophet,
            Movements movements, Synchro synchro) {
        super(world, prophet, movements);

        this.synchro = synchro;

        //we use the next players as our teammate eed one cycle to react
        this.expectedPlayers = this.getWorld().getNextPlayers();
        Arrays.fill(this.considerDir, true);

        // fill arrays
        for (int i = 0; i < this.numberPassDirs; i++) {
            this.interceptPoints[i] = new Vektor();
            this.dirs[i] = new Vektor(1, i * 360.0 / this.numberPassDirs);
            this.ballPosition[i] = new Vektor();
            this.ballSpeed[i] = new Vektor();
            this.maxBallSpeed[i] = new Vektor();
            this.interceptTimes[i] = this.emptyInt;
            this.interceptingPlayer[i] = this.emptyInt;
            this.nextInterceptingOpp[i] = this.emptyInt;
        }
        for (int i = 0; i < 22; i++) {

            if (!this.expectedPlayers[i].isGoalie()) {
                this.secureDistance[i] = this.expectedPlayers[i].getKickDistance();
            } else {
                this.secureDistance[i] = this.expectedPlayers[i].getCatchDistance();
            }

            if (i < 11) {
                this.secureDistance[i] -= this.securityMates;
            } else {
                this.secureDistance[i] -= this.securityOthers;
            }

        }
    }

    /**
     * resets the calculation data.
     *
     */
    private void reset() {
        for (int i = 0; i < this.numberPassDirs; i++) {
            this.interceptPoints[i].reset();
            this.interceptTimes[i] = this.emptyInt;
            this.interceptingPlayer[i] = this.emptyInt;
            this.nextInterceptingOpp[i] = this.emptyInt;
            this.considerDir[i] = true;

            this.interceptDistanceOpponent[i] = Integer.MAX_VALUE;
            this.interceptDistanceOwn[i] = Integer.MAX_VALUE;
            this.nearestOpponenentDistance[i] = Integer.MAX_VALUE;
        }

        Arrays.fill(this.considerDir, true);
        this.bestAction = null;

        //filter teammates that are offside, goalie, in our penalty area
        for (int i = 0; i < 11; i++) {

            if (this.getPlayer(i).isMe() || this.getPlayer(i).isGoalie() || (this.getPlayer(i).getPosition().y > this.getWorld().getOffsideY(true) && this.getWorld().getPlayMode() == PLAY_MODE.PLAY_ON) || (this.getWorld().getSelfRef().isGoalie() && this.getWorld().inOwnPenaltyArea(this.getPlayer(i)))) {
                this.isFilteredTeammate[i] = true;
            } else {
                this.isFilteredTeammate[i] = false;
            }
        }

        for (int i = 0; i < this.expectedPlayers.length; i++) {
            this.bodyDirs[i] = this.expectedPlayers[i].getBodyDir();
        }

    }

    /**
     * main routine which determines passing and scoring probabilities updating
     * the situation.
     */
    protected void update() {
        super.update();

        // reset result data
        this.passTo = this.emptyInt;
        this.resBall.copy(this.getWorld().getBallRef());
        this.benefit = 0.0;
        this.resDistanceOpponent = Integer.MAX_VALUE;
        this.resDistanceOwn = Integer.MAX_VALUE;
        this.resInterceptionTime = Integer.MAX_VALUE;

        //avoid the resource intensive pass calculation when already late,
        //scoring succeeds, or the world is nor VI based.
        if (!this.getWorld().getSelfRef().canKick(this.getWorld().getBallRef()) || !this.getWorld().isViBased() || this.synchro.getVIArrivalQuarter() > 2 || this.getProphet().getScoreBenefit() > 0.5) {
            if (false && this.synchro.getVIArrivalQuarter() > 2) {
                System.out.println(this.getWorld().getCycleAndPlayerString() + " SynchroQuarter:" + this.synchro.getVIArrivalQuarter());
            }
            return;
        }

        Ball b = this.getWorld().getBallRef();
        double accel;
        double bestAccelPercentage = 1;
        double bestValue = this.benefit;

        // set all directions and all data for each direction
        this.setDirs();
        this.setDirAges();
        this.setFilteredDirs();
        this.setAnglesAndDistancesOfPlayers();
        this.filterPlayersForDirs();

        //the goalie is only allowed a subset of directions to avoid dangerous
        //passes to the center
        if (this.getWorld().getSelfRef().isGoalie()) {
            this.filterGoalieDirs();
        }

        this.setPlayerOffset();
        this.setMaxTurnAngles();

        int synchroQuarter = this.synchro.getVIArrivalQuarter();

        //find the max ball accelerations for each direction
        for (int dir = 0; dir < this.numberPassDirs; dir++) {
            if (this.filteredDirs[dir]) {
                continue;
            }

            accel = this.getWorld().getSelfRef().getMaxBallAccelOptimized(b,
                    this.dirs[dir]);

            this.maxBallSpeed[dir].copy(this.dirs[dir]);
            this.maxBallSpeed[dir].setLength(accel);
        }

        // try to kick with different speed to the chosen direction and calculate
        // the benefit of each pass
        for (int accelIndex = 0; accelIndex < this.accelPercentage.length; accelIndex++) {

            // if we don't have time for the calculation try only fast kicking
            if (synchroQuarter == 2 && accelIndex > 0) {
                break;
            }

            this.reset();
            this.setPlayerPositionAndSpeed();

            //set the initial ball speed resulting form the accelPercentage
            for (int dir = 0; dir < this.numberPassDirs; dir++) {
                if (this.filteredDirs[dir]) {
                    continue;
                }

                accel = this.maxBallSpeed[dir].getLength() * this.accelPercentage[accelIndex];

                //if the resulting ball speed is to slow we don't pass
                if (accel < 0.4) {
                    this.considerDir[dir] = false;
                } else {
                    this.ballPosition[dir].copy(b.getPosition());
                    this.ballSpeed[dir].copy(this.maxBallSpeed[dir]);
                    this.ballSpeed[dir].mult(this.accelPercentage[accelIndex]);
                }
            }

            bestValue = this.calculateBestPassDir();

            // found new best pass
            if (bestValue > this.benefit) {
                this.benefit = bestValue;
                bestAccelPercentage = this.accelPercentage[accelIndex];
            }
        }

        if (this.passTo == this.emptyInt) {
            return;
        }

        //set the resulting ball
        this.resBall.setSpeed(this.dirs[this.bestDir]);
        this.resBall.getSpeed().mult(
                this.getWorld().getSelfRef().getMaxBallAccelOptimized(b,
                this.dirs[this.bestDir]) * bestAccelPercentage);

        // dummyVektor contains best accel vector
        this.dummyVektor.copy(this.dirs[this.bestDir]);
        this.dummyVektor.setLength(this.getWorld().getSelfRef().getMaxBallAccelOptimized(b, this.dirs[this.bestDir]) * bestAccelPercentage);

        this.bestAction = BasicActionFactory.accelerateBallToVelocity(this.getWorld().getSelfRef(), b, this.dummyVektor);

        //once found try to optimize the pass
        if (this.passTo != this.emptyInt) {
            this.correctedPass();
        }

    }

    /**
     * sets the age for each direction
     */
    private void setDirAges() {

        for (int dir = 0; dir < this.dirAge.length; dir++) {
            this.dirAge[dir] = this.getWorld().getAgeOfDir(this.dirAngles[dir]);
        }
    }

    /**
     * passing is very time consuming so if we are running on a slow machine and
     * we already had some holes when with ball we reduce the number of pass
     * dirs.
     */
    private void setFilteredDirs() {

        if (this.getWorld().getPlayMode() == PLAY_MODE.KICK_OFF_OWN) {
            for (int dir = 0; dir < this.filteredDirs.length; dir++) {
                if (Math.abs(this.dirAngles[dir]) < 90) {
                    this.filteredDirs[dir] = true;
                } else {
                    this.filteredDirs[dir] = false;
                }
            }
            return;
        }

        double holePercentage = this.synchro.getWithBallHolePercentage();

        int synchroQuarter = this.synchro.getVIArrivalQuarter();
        double usedDirPercentage = 1.0;

        if (holePercentage > 0.05) {
            usedDirPercentage /= 2.0;
        } else if (holePercentage > 0.01) {
            usedDirPercentage /= 1.3;
        } else {
            usedDirPercentage /= 1.0;
        }

        switch (synchroQuarter) {
            case 0:
                break;

            case 1:
                usedDirPercentage /= 2.0;
                break;

            case 2:
                usedDirPercentage /= 5.0;
                break;

            default:
                usedDirPercentage /= 10.0;
                break;
        }

        int mod = (int) (this.numberPassDirs * usedDirPercentage);

        for (int dir = 0; dir < this.numberPassDirs; dir++) {
            if (dir % mod == 0) {
                this.filteredDirs[dir] = true;
            } else {
                this.filteredDirs[dir] = false;
            }
        }
    }

    /**
     * optimizes the speed of the pass when the pass is secure.
     */
    private void correctedPass() {
        //only needed when the ball arrives very fast and only done when no
        //opponent is near
        if (this.resBall.getSpeed().getLength() * Math.pow(SConf.getInstance().BALL_DECAY,
                this.resInterceptionTime) < 1 || ((this.resNearestOpponentDistance < 1.0 || this.resDistanceOpponent < 1) && this.resInterceptionTime > 4) || this.resDistanceOwn > 2 + this.getPlayer(this.passTo).getAge(this.getWorld().getBodyCycle(),
                UPDATE_MODE.META_BASED_ON_INFO)) {
            return;
        }

        //slow down the ball by this number of cycles
        int delay = this.resNearestOpponentDistance > 3 ? 1 : 0;

        if (this.resBall.getSpeed().getLength() > 1.0) {
            delay += 1;
        }

        //more delay if the receiver wasn't seen lately
        if (this.getPlayer(this.passTo).getAge(this.getWorld().getBodyCycle(),
                UPDATE_MODE.META_BASED_ON_INFO) > 2) {
            delay += 1;
            delay += this.getPlayer(this.passTo).getAge(
                    this.getWorld().getBodyCycle(),
                    UPDATE_MODE.META_BASED_ON_INFO) % 5;
        }

        //more delay if the interception angle is near 90 degree as in this case
        //the chance that the receiver misses the ball is higher
        double interceptAngle = this.getPlayer(this.passTo).getAngleTo(
                this.resIP);

        if (Math.abs(Math.abs(Vektor.normalize(180.0 + interceptAngle + this.dirAngles[this.bestDir])) - 90.0) < 30) {
            delay += 1;
        }

        delay = Math.min((int) this.resDistanceOpponent - 1 + (this.resInterceptionTime % 5), delay);
        delay = Math.min(3, delay);

        //get the delayed kickaction and set the best pass data
        KickAction kA = BasicActionFactory.kickInTime(this.getWorld().getSelfRef(), this.getWorld().getBallRef(), this.resIP,
                this.resInterceptionTime + delay, 0);

        if (kA != null) {
            this.resBall.copy(this.getWorld().getBallRef());
            this.resBall.predictThisAfterAction(this.getWorld().getSelfRef(),
                    kA);
            this.resBall.getPosition().copy(
                    this.getWorld().getBallRef().getPosition());
            this.resBall.getSpeed().mult(1.0 / SConf.getInstance().BALL_DECAY);

            this.bestAction = kA;
        }
    }

    /**
     * simulate passing after values are set.
     *
     * simulates the ball movement for all directons and calculates who intercepts
     * first.
     */
    private void simulate() {

        // we kicked the ball
        this.extrapolateBalls();

        // and drifted once
        this.extrapolatePlayerPosition();

        //the distnce extrapolated player to extrapolated ball
        double distance;

        // the percentage of perfect player movement
        double imperfectPercentage = 1.0;

        // for all cycles
        for (int cycle = 1; cycle <= this.maxCycles; cycle++) {

            //add some noise
            if (cycle > 10) {
                imperfectPercentage = 0.9;
            } else if (cycle > 4) {
                imperfectPercentage = 0.95;
            }

            // and all directions
            for (int dir = 0; dir < this.numberPassDirs; dir++) {

                // dir is already out of game
                if (!this.considerDir[dir]) {
                    continue;
                }

                //don't make distant backward passes
                if (cycle > 20 && (Math.abs(this.dirAngles[dir]) > 120)) {
                    this.considerDir[dir] = false;
                    continue;
                }

                // ball is outside -> don't consider dir
                if (!this.getWorld().inField(this.ballPosition[dir])) {
                    this.considerDir[dir] = false;
                    continue;
                }

                this.interceptDistanceOpponent[dir] = Integer.MAX_VALUE;
                this.interceptDistanceOwn[dir] = Integer.MAX_VALUE;

                // for all players
                for (int playerIndex = 0; playerIndex < this.expectedPlayers.length; playerIndex++) {
                    // dir was set to false so don't consider it
                    if (!this.considerDir[dir] && playerIndex < 11) {
                        continue;
                    }

                    if (!this.considerPlayers[dir][playerIndex]) {
                        continue;
                    }

                    // only opponents if a teammate already intercepted
                    if (this.interceptTimes[dir] != this.emptyInt && playerIndex < 11) {
                        continue;
                    }

                    int j = cycle + this.playerOffset[playerIndex];
                    if (j < 0) {
                        continue;
                    }

                    j = Math.min(this.maxCycles - 1, j);

                    //get the distance
                    distance = this.getMovements().getLeftDistance(playerIndex,
                            j, this.ballPosition[dir],
                            this.playerPosition[playerIndex],
                            this.bodyDirs[playerIndex],
                            this.maxTurnAngle[playerIndex][0],
                            this.maxTurnAngle[playerIndex][1],
                            this.secureDistance[playerIndex],
                            imperfectPercentage);

                    if (playerIndex < 11) {
                        if (distance < this.interceptDistanceOwn[dir]) {
                            this.interceptDistanceOwn[dir] = distance;
                        }
                    } else {
                        distance -= 0.15;

                        if (distance < this.interceptDistanceOpponent[dir]) {
                            this.interceptDistanceOpponent[dir] = distance;
                        }
                        if (distance < this.nearestOpponenentDistance[dir]) {
                            this.nearestOpponenentDistance[dir] = distance;
                        }
                    }

                    //someone intercepts -> remember this player
                    if (distance < 0) {
                        this.finalizeDir(cycle, dir, playerIndex);
                        continue;
                    } else {
                        continue;
                    }
                }
            }

            //extrapolate ball and players
            this.extrapolateBalls();
            this.extrapolatePlayerPosition();
        }
    }

    /**
     * an intercepting player was found so we have to finalize the direction.
     *
     * @param cycle - the interception time
     * @param dir - the pass direction
     * @param player - the receiver
     */
    private void finalizeDir(int cycle, int dir, int player) {
        // first intercepting player
        if (this.interceptTimes[dir] == this.emptyInt) {
            this.considerDir[dir] = false;

            //don't pass to opponents or to the goalie.
            if (player > 10 || player == 0 || this.isFilteredTeammate[player] || (this.getWorld().getSelfRef().isGoalie() && this.getWorld().getPlayMode() == PLAY_MODE.GOAL_KICK_OWN && this.getWorld().inOwnPenaltyArea(
                    this.ballPosition[dir].x,
                    this.ballPosition[dir].y, 1.0))) {
                return;
            }

            this.interceptTimes[dir] = cycle;
            this.interceptingPlayer[dir] = player;
            this.interceptPoints[dir].copy(this.ballPosition[dir]);
        }
    }

    /**
     * choose the best pass direction. this method evaluates every possible pass
     * and sets the pass with the highest benefit.
     *
     */
    private double calculateBestPassDir() {
        this.simulate();

        double maxValue = this.benefit;
        double value;
        int security;

        //for each dir
        for (int dir = 0; dir < this.dirs.length; dir++) {
            if (this.filteredDirs[dir]) {
                continue;
            }

            // interception by teammate
            if (this.interceptingPlayer[dir] < 11) {

                double distanceOpp = this.interceptDistanceOpponent[dir];

                if (distanceOpp < 0) {
                    security = 0;
                } else {
                    security = Math.min(5, (int) (distanceOpp / 1.2) + 1);
                }

                //team left for receiver before opponent gets near the interception
                //point
                int securityDelay = (int) (this.getPlayer(
                        this.interceptingPlayer[dir]).getAge(
                        this.getWorld().getBodyCycle(),
                        UPDATE_MODE.META_BASED_ON_INFO) / 2.0 + this.dirAge[dir] / 2.0 + 0.5);

                security = Math.max(security - securityDelay, 0);

                //the benefit for the situation resulting when passing
                value = SituationEvaluator.getValue(
                        SituationEvaluator.TYPE.PASSING, this.getWorld().getBallRef().getPosition(),
                        this.interceptPoints[dir], security,
                        this.interceptTimes[dir], this.getPlayer(this.interceptingPlayer[dir]), this.getWorld());

                //if a better pass was found, store it
                if (value > maxValue) {
                    value = this.correctValueByOutFact(value, this.bestDir);
                    value = this.correctValueByNearestOpponentFact(value,
                            this.bestDir);
                    if (value > maxValue) {
                        maxValue = value;
                        this.resIP.copy(this.interceptPoints[dir]);
                        this.passTo = this.interceptingPlayer[dir];
                        this.bestDir = dir;
                        this.resDistanceOpponent = this.interceptDistanceOpponent[dir];
                        this.resDistanceOwn = this.interceptDistanceOwn[dir];
                        this.resNearestOpponentDistance = this.nearestOpponenentDistance[dir];
                        this.resInterceptionTime = this.interceptTimes[dir];
                    }
                }
            } else {
            }
        }
        return maxValue;
    }

    /**
     * corrects a pass value when the pass might go out.
     *
     * @param value -
     *            previous value
     * @param bestDir -
     *            bestDir index
     * @return the corrected value (same as the value when no danger)
     */
    private double correctValueByOutFact(double value, int bestDir) {

        this.dummyVektor.copy(this.ballPosition[bestDir]);

        for (int i = 1; i < 4; i++) {
            dummyVektor.addToThis(this.ballSpeed[bestDir]);
            if (!this.getWorld().inField(this.dummyVektor)) {
                return value * i / 4;
            }
        }

        return value;
    }

    /**
     * corrects the pass value if an opponent gets very near the pass line
     *
     * @param value - the benefit
     * @param bestDir - the best direction
     * @return the corrected benefit
     */
    private double correctValueByNearestOpponentFact(double value, int bestDir) {

        if (this.nearestOpponenentDistance[bestDir] < 0.5) {
            return value * 0.8;
        }

        return value;
    }

    /**
     * extrapolates all balls by 1 cycle.
     *
     */
    private void extrapolateBalls() {
        for (int dir = 0; dir < this.ballPosition.length; dir++) {
            if (!this.filteredDirs[dir] && this.considerDir[dir]) {
                this.ballPosition[dir].addToThis(this.ballSpeed[dir]);
                this.ballSpeed[dir].mult(0.94);
            }
        }
    }

    /**
     * the player moves also without dashing (drift). This method accumulates
     * this drifting and sets the players new position and speed.
     *
     */
    private void extrapolatePlayerPosition() {
        for (int i = 0; i < this.playerPosition.length; i++) {

            this.playerPosition[i].addToThis(this.playerSpeed[i]);
            this.playerSpeed[i].mult(this.expectedPlayers[i].getPConf().PLAYER_DECAY);
        }
    }

    /**
     * sets values for distances and angle to/of players
     */
    private void setAnglesAndDistancesOfPlayers() {

        for (int i = 0; i < 22; i++) {
            if (this.getPlayer(i).isMe()) {
                this.angleToPlayer[i] = 0;
                this.distanceToPlayer[i] = 0;
            } else {
                this.angleToPlayer[i] = this.getWorld().getSelfRef().getAngleTo(this.getPlayer(i));
                this.distanceToPlayer[i] = this.getWorld().getSelfRef().getDistance(this.getPlayer(i));
            }
        }

    }

    /**
     * sets flags that tell which player has to be considered for which pass
     * direction (used only to improve performance)
     */
    private void filterPlayersForDirs() {
        double angleDiff;
        double dirAngle;

        // for all dirxPlayer combinations
        for (int dir = 0; dir < this.dirs.length; dir++) {
            if (this.filteredDirs[dir]) {
                continue;
            }

            dirAngle = this.dirAngles[dir];

            for (int player = 0; player < 22; player++) {
                angleDiff = Math.abs(Vektor.normalize(dirAngle - this.angleToPlayer[player]));
                if ((angleDiff > MAX_TOLERATED_ANGLE_TO_PLAYER && this.distanceToPlayer[player] > MAX_NEAR_DISTANCE_TO_PLAYER) || !this.getPlayer(player).isReliable()) {
                    this.considerPlayers[dir][player] = false;
                } else {
                    this.considerPlayers[dir][player] = true;
                }
            }
        }
    }

    /**
     * sets the dirs. the density is higher in goal direction.
     *
     */
    private void setDirs() {
        double dirToGoal = this.getWorld().getSelfRef().getAngleTo(
                SConf.getInstance().GOAL_POS_OTHER);
        boolean iAmUnderGoal = dirToGoal < 0;
        // if the angle to goal is high the player is in a corner of the field
        // this factor helps to prefer directions in field dir
        int offsetFactor = (int) dirToGoal / 30;
        int dirCounter = 0;

        // the number of directions for all slices of 45° is set
        for (int i = 0; i < 4; i++) {
            this.numberDirsLeft[i] = (int) (this.dirImportance[i] * this.numberPassDirs);
            this.numberDirsRight[i] = (int) (this.dirImportance[i] * this.numberPassDirs);
            dirCounter += this.numberDirsLeft[i] + this.numberDirsRight[i];

            this.numberDirsLeft[i] -= offsetFactor;
            this.numberDirsRight[i] += offsetFactor;

            // correct if negativ - left
            if (this.numberDirsLeft[i] < 0) {
                this.numberDirsRight[i] += this.numberDirsLeft[i];
                this.numberDirsLeft[i] = 0;
            }
            // correct if negativ - right
            if (this.numberDirsRight[i] < 0) {
                this.numberDirsLeft[i] += this.numberDirsRight[i];
                this.numberDirsRight[i] = 0;
            }
        }

        // if directions were left use them in most important slice
        while (dirCounter < this.numberPassDirs) {
            if (iAmUnderGoal) {
                this.numberDirsLeft[0]++;
            } else {
                this.numberDirsRight[0]++;
            }
            dirCounter++;
        }

        double intervalSize;
        double intervalCounter;

        int part = 0;
        dirCounter = 0;

        // right side -> clockwise from 0 to 180
        while (part < 4) {
            if (this.numberDirsRight[part] == 0) {
                part++;
                continue;
            }

            // angle between 2 directions
            intervalSize = (double) 45.0 / this.numberDirsRight[part];
            intervalCounter = 0;
            while (this.numberDirsRight[part] > 0) {
                this.dirs[dirCounter].pointAtPolar(1.0, dirToGoal + part * 45.0 + intervalSize * intervalCounter);
                this.dirAngles[dirCounter] = Vektor.normalize(dirToGoal + part * 45.0 + intervalSize * intervalCounter);
                this.numberDirsRight[part]--;
                dirCounter++;
                intervalCounter++;
            }
            part++;
        }

        part = 3;

        // left side -> clockwise from -180 to 0
        while (part > -1) {
            if (this.numberDirsLeft[part] == 0) {
                part--;
                continue;
            }

            intervalSize = (double) 45.0 / this.numberDirsLeft[part];
            intervalCounter = 0;
            while (this.numberDirsLeft[part] > 0) {
                this.dirs[dirCounter].pointAtPolar(1.0, dirToGoal + 180 + (3 - part) * 45.0 + intervalSize * intervalCounter);
                this.dirAngles[dirCounter] = Vektor.normalize(dirToGoal + 180 + (3 - part) * 45.0 + intervalSize * intervalCounter);
                this.numberDirsLeft[part]--;
                dirCounter++;
                intervalCounter++;
            }
            part--;
        }
    }

    /**
     * the goalie isn't allowed to pass in the middle
     *
     * @param goaliePos
     */
    private void filterGoalieDirs() {
        if (!this.getWorld().inOwnPenaltyArea(this.getWorld().getSelfRef())) {
            return;
        }

        boolean left = this.getWorld().getSelfRef().getPosition().x < 0;

        for (int i = 0; i < this.dirs.length; i++) {
            if ((left && this.dirAngles[i] > 0) || (!left && this.dirAngles[i] < 0)) {
                this.considerDir[i] = false;
            } else {
                this.considerDir[i] = true;
            }
        }
    }

    /**
     * players that were not seen might be nearer than expected. own players
     * need one cycle to react.
     *
     */
    private void setPlayerOffset() {
        int j = 0;
        for (int playerIndex = 0; playerIndex < this.expectedPlayers.length; playerIndex++) {
            j = 0;
            if (playerIndex < 11) {
                if (this.expectedPlayers[playerIndex].getAge(this.getWorld().getBodyCycle(), UPDATE_MODE.META_BASED_ON_INFO) == 0) {
                    j -= 1;
                } else {
                    j -= 2;
                }
            } else {
                j += Math.min(3, this.expectedPlayers[playerIndex].getAge(this.getWorld().getBodyCycle(),
                        UPDATE_MODE.META_BASED_ON_INFO));
            }
            j -= this.getPlayer(playerIndex).getTackledeadCountdown();
            this.playerOffset[playerIndex] = j;
        }
    }

    /**
     * set the max turn angles for every player based on the players speed.
     * (used for efficiency)
     *
     */
    private void setMaxTurnAngles() {
        for (int playerIndex = 0; playerIndex < this.expectedPlayers.length; playerIndex++) {
            this.maxTurnAngle[playerIndex][0] = this.getPlayer(playerIndex).getMaxTurningAngle1();
            this.maxTurnAngle[playerIndex][1] = this.getPlayer(playerIndex).getMaxTurningAngle2();
        }
    }

    /**
     * sets the position and speed arrays for each player. (used for efficiency)
     *
     */
    private void setPlayerPositionAndSpeed() {
        for (int i = 0; i < this.expectedPlayers.length; i++) {
            this.playerPosition[i].copy(this.expectedPlayers[i].getPosition());
            this.playerSpeed[i].copy(this.expectedPlayers[i].getSpeed());
        }
    }

    /**
     * playerIndex -> player
     *
     * @param index
     * @return
     */
    private Player getPlayer(int index) {
        return this.expectedPlayers[index];
    }

    /** found someone to pass to.
     *
     * @return true if passin is possible
     */
    public boolean canPass() {
        if (!wasUpdated()) {
            this.update();
        }

        return this.passTo != this.emptyInt;
    }

    /**
     * @return Returns the receivers number. Integer.MAX_VALUE if no passing
     * possible
     */
    public Player getPassTo() {
        if (!wasUpdated()) {
            this.update();
        }
        if (this.passTo == Integer.MAX_VALUE) {
            return null;
        }

        return this.getPlayer(this.passTo);
    }

    /**
     * @return Returns the resulting ip.
     */
    public Vektor getResIP() {
        if (!wasUpdated()) {
            this.update();
        }
        return this.resIP;
    }

    /**
     * the ball that results from passing.
     *
     * @return the resulting ball
     */
    public Ball getResBall() {
        if (!wasUpdated()) {
            this.update();
        }
        return this.resBall;
    }
}
